# -*- coding: utf-8 -*-
# standard
import os
import sys
import time
import platform
import asyncio
from datetime import date
from typing import Callable, Annotated
from contextlib import asynccontextmanager

# third
import uvicorn
from fastapi import FastAPI
from fastapi import WebSocket
from fastapi.routing import APIRoute
from fastapi import Depends
from fastapi import Query as QueryParam
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import Response
from fastapi.responses import JSONResponse
from fastapi.responses import FileResponse
from fastapi.responses import RedirectResponse
from fastapi.responses import HTMLResponse
from fastapi.responses import RedirectResponse
from fastapi.requests import Request
from fastapi.exceptions import HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.exceptions import StarletteHTTPException
from fastapi.templating import Jinja2Templates
from fastapi.security import OAuth2PasswordRequestForm
from fastapi.openapi.docs import get_swagger_ui_html

# local
_P_PATH = os.path.dirname(os.path.realpath(__file__))
if _P_PATH not in sys.path:
    sys.path.append(_P_PATH)
from views import import_views
from toolbox.common_tools import get_fastapi_real_ip
from toolbox.common_tools import check_env
from toolbox.common_tools import logger
from toolbox.common_tools import INCLUDE_SCHEMA
from models import *



"""
@Title:   
@File: main_server.py
@Author: walle 2024年12日08日 10时08分04秒
@Version: 1.0.0
@Desc: 
"""


if platform.system() == "Windows":  # 如果是windows系统
    # 设置事件循环策略防止在客户端突然 断开时可能出现的出现 OSError: [WinError 64]
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())  


@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时执行初始化
    # app 参数暂时没用上
    try:
        await init_resources()
        # 初始化其他服务（如Redis、消息队列）
        yield  # 初始化完成，返回
    except Exception as e:
        logger.error(f"初始化失败: {e}")
        logger.exception(e)
        # 关闭时执行清理
        await cleanup_resources()

ENV = check_env() 
if ENV != "dev": # 非开发环境 关闭文档
    app = FastAPI(docs_url=None, redoc_url=None, lifespan=lifespan)    # 初始化fastapi app lifespan 用来注册生命周期函数
else:  # 开发环境 开启文档， 其中docs_url=None 表示启用自定义docs文档
    app = FastAPI(docs_url=None, redoc_url="/redos", lifespan=lifespan)    # 初始化fastapi app lifespan 用来注册生命周期函数
app.mount("/static", StaticFiles(directory="static"), name="static")
import_views(app=app)



PORT = 12005
origins = ['*']  # debug模式允许所有的主机访问
app.add_middleware(
    CORSMiddleware,  # 跨域的中间件
    allow_origins=origins,  # 允许跨域的网址列表
    allow_credentials=True,  # 允许跨域的 https
    allow_methods=["*"],  # 允许跨域的方法
    allow_headers=["*"],  # 允许跨域的请求头信息
)



@app.get("/health", tags=["hello world"], response_class=Response, include_in_schema=INCLUDE_SCHEMA)
def hello_world_vew():
    """
    hello_world
    :return:
    """
    return Response("hello world!")


@app.get("/docs", include_in_schema=False)  # include_in_schema=False 不出现在自动文档中.
async def custom_swagger_ui_html(
    uid: str = QueryParam(None, title="当前日期", description="当前日期"),
):
    """
    自定义 swagger_ui的文档路由
    用于解决 swagger-ui-bundle.js 和  swagger-ui.css 有时候因为网络受限无法访问的问题
    swagger_ui 仓库地址: https://github.com/swagger-api/swagger-ui
    """
    today = date.today().strftime("%Y%m%d")
    if ENV != "dev" and uid != today:
        logger.warning(f"ENV = {ENV}, uid = {uid}, today = {today}")
        raise HTTPException(status_code=404, detail="not found")
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=app.title + " - Swagger UI",
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        swagger_js_url="/static/js/swagger-ui-bundle.js",
        swagger_css_url="/static/css/swagger-ui.css",
    )



###############################  中间件和 自定义函数 ##################################


@app.middleware(middleware_type="http")
async def middleware_example(request: Request, call_next: Callable):
    """
    这是一个中间件，当前的设计使其相当于 flask的 before_request 和 after_request 之类的函数
    :param request: 请求体
    :param call_next: 原来的视图函数
    :return:
    """
    user_agent = request.headers.get("user-agent")
    if user_agent and 'censys.io' in user_agent:  # 屏蔽 censys.io 搜索引擎
        raise HTTPException(status_code=404, detail="404")
    # 取走 body （form和json都是body内容）会导致文件上传失败。
    # form = await request.form()
    # form_data = {k: v for k, v in form.items()}
    # json_data = {}
    # try:
    #     json_data = await request.json()
    # except:
    #     pass
    # 放行 
    t1 = time.perf_counter()
    resp = await call_next(request)
    t2 = time.perf_counter()
    d = round(t2 - t1, 6)
    if d > 0.3:
        logger.warning(f"请求路径: {request.url.path}，响应耗时: {d}秒")  
    else:
        logger.debug(f"请求路径: {request.url.path}，响应耗时: {d}秒")  
    return resp


# 自定义 404 异常处理器  
@app.exception_handler(StarletteHTTPException)  
async def http_exception_handler(request: Request, exc: StarletteHTTPException):  
    if exc.status_code == 404:  
        # 记录日志  
        mes = f"ip:{get_fastapi_real_ip(request)}, host:{request.url.hostname}, path:{request.url.path}, authorization: {request.headers.get('authorization')}, referrer:{request.headers.get('referer')}, user_agent: {request.headers.get('user-agent')}"
        logger.error(mes)  
    # 你可以自定义返回给客户端的响应  
    return JSONResponse(  
        status_code=exc.status_code,  
        content={"detail": exc.detail}  
    )  


def get_workers() -> int:
    """
    从环境变量中取得 workers
    注意需要在 service.conf 中进行对应的配置
    """
    workers = os.getenv("workers", 1)
    if isinstance(workers, str):
        try:
            workers = int(workers)
        except:
            workers = 1
    return workers


if __name__ == '__main__':
    import colorama
    color = colorama.Fore.CYAN
    workers = get_workers()
    print(color + "打印全部路由")
    for x in app.routes:
        logger.debug(color + getattr(x, "path").strip())
    logger.debug(color + "打印全部路由结束")
    logger.info(f"当前工作环境: {ENV}, workers = {workers}")
    start_init_default_thread() # 无论多少 个worker，都只会执行一次
    # 以编码方式启动服务
    uvicorn.run(app="main_server:app", port=PORT, host="0.0.0.0", workers=workers, reload=True, log_level="info")
    pass